/*+*********************************************************
Filename: B03_fft-ip_app\src\top.v
Description:
  Learning the usage of Gowin FFT IP.
  Try to process the mic data through FFT and IFFT and then output to dac.

Modification:
2024.03.22 creation by H.Zheng
           copied from 02_mic_dac01 and then add fft and ifft and fifo. (remove uart)
2024.04.10 add FSM, add data delay between FFT and IFFT
2024.04.10 copied from 05_fft-ip_step01, change FFT to 1024*24 and 
           change FIFO to 1024*24, used Full Flag to invoke FFT convertion

**********************************************************-*/


module top(
	input wire clk,
	input wire rst_n,
  output wire [5:0] led, 

  output wire mic_ws,
  output wire mic_ck,
  output wire mic_lr,
  input wire mic_data,

  output wire dac_ws,
  output wire dac_bck,
  output wire dac_data,
  output wire dac_pa_en
);

/**
 * clock section
 */
  wire clk1m;
  wire clk6m;
  PLL_6M PLL6m(
    .clkout(clk6m),
    .clkoutd(clk1m),
    .clkin(clk)
  );


/**
 * mic section
 */
  assign mic_lr = 1'b0;

  reg [7:0] mic_counter;

  always @(posedge clk6m or negedge rst_n) begin
    if (!rst_n)
      mic_counter <= 8'd0;
    else
      mic_counter <= mic_counter + 1'd1;
  end

  wire clk_3072kHz_n = mic_counter[0];  //6/2
  wire clk_48kHz_n   = mic_counter[6]; //3/64=6/128

  assign mic_ck = clk_3072kHz_n;
  assign mic_ws = clk_48kHz_n;


  //
  // receive mic data
  //
  reg [63:0] shift_reg;

  always @(posedge mic_ck) begin
    shift_reg <= {shift_reg[62:0], mic_data};	
  end	
      
  reg [63:0] data_reg;
  always  @(negedge mic_ws) begin
    data_reg <= shift_reg;
  end		

  wire[23:0] data_l = data_reg[62:39];	
  wire[23:0] data_r = data_reg[30:7];	    

  //assume that it's signed data    
  wire[22:0] l_amplitude = data_l[23] ? ((~data_l[22:0])+1'b1) : data_l[22:0];
  wire loud_voice = (l_amplitude[22:16]>=7'h07) ? 1'b1 : 1'b0;    

  //trigger signal
  reg [1:0] mic_ws_edge;
  always @(negedge clk) begin
    mic_ws_edge <= {mic_ws_edge[0], mic_ws};	
  end
  wire tx_data_valid = (~mic_ws_edge[1]) & mic_ws_edge[0]; //posedge of mic_ws trigger a data transfer

/**
 * input fifo
 */
  wire [23:0] micdata = data_l;
  wire infifo_full_flag;
  wire infifo_rd_clk;
  wire infifo_rd_en;
  wire [23:0] infifo_outdata;

	FIFO_1024x24_Top m_input_fifo(
		.Data(micdata), //input [23:0] Data
		.WrClk(mic_ws), //input WrClk
		.RdClk(infifo_rd_clk), //input RdClk
		.WrEn(1'b1), //input WrEn
		.RdEn(infifo_rd_en), //input RdEn
		.Q(infifo_outdata), //output [23:0] Q
		.Empty(), //output Empty
		.Full(infifo_full_flag) //output Full
	);

/**
 * FFT
 */
  wire mfft_busy_flag;
  wire mfft_input_data_flag;
  wire mfft_start_output_data_flag;
  wire mfft_output_data_flag;
  reg mfft_start;
  wire [23:0] mfft_xk_re_o, mfft_xk_im_o;

  wire [23:0] xn_re_i = infifo_outdata;
  wire mfft_clk = clk1m;
  wire mfft_rst = ~rst_n;

  wire mfft_end_of_output_data_flag;
  wire mfft_start_input_data_flag;

	FFT_Top_1024p m_fft(
		.idx(), //output [9:0] idx
		.xk_re(mfft_xk_re_o), //output [23:0] xk_re
		.xk_im(mfft_xk_im_o), //output [23:0] xk_im
		.sod(mfft_start_input_data_flag), //output sod
		.ipd(mfft_input_data_flag), //output ipd
		.eod(), //output eod
		.busy(mfft_busy_flag), //output busy
		.soud(mfft_start_output_data_flag), //output soud
		.opd(mfft_output_data_flag), //output opd
		.eoud(mfft_end_of_output_data_flag), //output eoud
		.xn_re(xn_re_i), //input [23:0] xn_re
		.xn_im(24'b0), //input [23:0] xn_im
		.start(mfft_start), //input start
		.clk(mfft_clk), //input clk
		.rst(mfft_rst), //input rst
		.ifft(1'b0) //input ifft
	);

  //trigger input-fifo
  assign infifo_rd_clk = mfft_input_data_flag & mfft_clk;
  assign infifo_rd_en = 1'b1;

//FSM
  localparam S_FFT_IDLE = 1;
  localparam S_FFT_START = 2;
  localparam S_FFT_STARTINPUT = 3;
  localparam S_FFT_ENDOFOUTPUT = 4;

  reg[2:0] fft_state;
  reg[2:0] fft_next_state;

  wire fsm_clk = ~mfft_clk;

  always@(posedge fsm_clk or negedge rst_n) begin
    if (!rst_n)
      fft_state <= S_FFT_IDLE;
    else
      fft_state <= fft_next_state;
  end

  always@(*) begin
    case(fft_state)
      S_FFT_IDLE:
        if(infifo_full_flag == 1'b1)
          fft_next_state <= S_FFT_START;
        else
          fft_next_state <= S_FFT_IDLE;
      S_FFT_START:
        if(mfft_start_input_data_flag == 1'b1)
          fft_next_state <= S_FFT_STARTINPUT;
        else
          fft_next_state <= S_FFT_START;
      S_FFT_STARTINPUT:
        if(mfft_end_of_output_data_flag == 1'b1)
          fft_next_state <= S_FFT_ENDOFOUTPUT;
        else
          fft_next_state <= S_FFT_STARTINPUT;
      S_FFT_ENDOFOUTPUT:
        fft_next_state <= S_FFT_IDLE;
      default:
        fft_next_state <= S_FFT_IDLE;
    endcase
  end

  always@(posedge fsm_clk or negedge rst_n)
  begin
    if(!rst_n)
      mfft_start <= 1'b0;
    else if(fft_state == S_FFT_START)
      mfft_start <= 1'b1;
    else
      mfft_start <= 1'b0;
  end


/**
 * IFFT
 */
  reg [23:0] mifft_xn_re, mifft_xn_im;
  wire [23:0] mifft_xk_re_o, mifft_xk_im_o;
  wire mifft_output_data_flag;
  wire mifft_start;
  wire mifft_busy_flag;

  //since mfft_start_output_data_flag and first mifft_xk_re_o output are at same fft clk, must delay mifft_xk_re_o
  // to match the timing of iFFT

  //shift FFT's output data as IFFT's input
  reg [23:0] mifft_xn_re_delay, mifft_xn_im_delay;
  always @(negedge mfft_clk) begin
    mifft_xn_re_delay <= mfft_xk_re_o;	
    mifft_xn_im_delay <= mfft_xk_im_o;	
  end  
  always @(posedge mfft_clk) begin
    mifft_xn_re <= mifft_xn_re_delay;	
    mifft_xn_im <= mifft_xn_im_delay;	
  end  

	FFT_Top_1024p m_ifft(
		.idx(), //output [9:0] idx
		.xk_re(mifft_xk_re_o), //output [23:0] xk_re
		.xk_im(mifft_xk_im_o), //output [23:0] xk_im
		.sod(), //output sod
		.ipd(), //output ipd
		.eod(), //output eod
		.busy(mifft_busy_flag), //output busy
		.soud(), //output soud
		.opd(mifft_output_data_flag), //output opd
		.eoud(), //output eoud
		.xn_re(mifft_xn_re), //input [23:0] xn_re
		.xn_im(mifft_xn_im), //input [23:0] xn_im
		.start(mifft_start), //input start
		.clk(mfft_clk), //input clk
		.rst(mfft_rst), //input rst
		.ifft(1'b1) //input ifft
	);
  //trigger ifft start by mfft_start_output_data_flag
  assign mifft_start = mfft_start_output_data_flag;

/**
 * output fifo
 */

  wire [23:0] outfifo_indata;
  wire outfifo_wr_clk;
  wire outfifo_wr_en;
  wire outfifo_rd_clk;
  wire outfifo_rd_en;
  wire [23:0] outfifo_outdata;

	FIFO_1024x24_Top m_output_fifo(
		.Data(outfifo_indata), //input [23:0] Data
		.WrClk(outfifo_wr_clk), //input WrClk
		.RdClk(outfifo_rd_clk), //input RdClk
		.WrEn(outfifo_wr_en), //input WrEn
		.RdEn(outfifo_rd_en), //input RdEn
		.Q(outfifo_outdata), //output [23:0] Q
		.Empty(), //output Empty
		.Full() //output Full
	);

  assign outfifo_indata = mifft_xk_re_o;
  assign outfifo_wr_clk = ~mfft_clk; //clk in data via negedge
  assign outfifo_wr_en = mifft_output_data_flag;

  //to DAC
  assign outfifo_rd_en = 1'b1;
  assign outfifo_rd_clk = mic_ws; //tx_data_valid;

/**
 * DAC interface
 */

  wire [15:0] dac_data_r, dac_data_l;

  assign dac_bck = mic_counter[1]; //32 bck per ws period

//  assign dac_data_l = outfifo_outdata[23:8];
  assign dac_data_l = {outfifo_outdata[23], outfifo_outdata[14:0]};
  assign dac_data_r = {outfifo_outdata[23], outfifo_outdata[14:0]};

  dac_pt8211 pt8211_u1(
    .data_in_clk(~outfifo_rd_clk),
    .data_r(dac_data_r),
    .data_l(dac_data_l),
    .dac_if_bck(dac_bck),
    .dac_if_ws(dac_ws),
    .dac_if_data(dac_data)
  );

  assign dac_pa_en = 1'b1;

/**
 * led
 */

//  wire fft_out_zero_re = (mfft_xk_re_o == 0);
//  wire fft_out_zero_im = (mfft_xk_im_o == 0);
//  wire ifft_out_zero_re = (mifft_xk_re_o == 0);
//  wire ifft_out_zero_im = (mifft_xk_im_o == 0);
  wire outfifo_outdata_zero = (outfifo_outdata == 24'b0);
  wire[22:0] amplitude2 = outfifo_outdata[23] ? ((~outfifo_outdata[22:0])+1'b1) : outfifo_outdata[22:0];
  wire loud_voice2 = (amplitude2[22:8]>=15'h07) ? 1'b1 : 1'b0;    

  assign led = {~loud_voice2, 3'b111, ~outfifo_outdata_zero , ~loud_voice};
//  assign led = {fft_out_zero_re, fft_out_zero_im, ifft_out_zero_re, ifft_out_zero_im, outfifo_outdata_zero , ~loud_voice};
//  assign led = {~mifft_output_data_flag, ~outfifo_af_flag, ~Almost_Full_o, ~mfft_busy_flag, ~mifft_busy_flag , ~loud_voice};

//  assign led = {5'b11111 , ~loud_voice};
//  assign led = ~l_amplitude[22:17];

endmodule